package de.scs.core;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimerTask;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import org.apache.log4j.Logger;

import de.scs.beans.InsulinPump;
import de.scs.constants.BatteryLevel;
import de.scs.constants.BloodSugarLevelConstants;
import de.scs.constants.HardwareCheckConstants;
import de.scs.constants.InsulinDoseConstants;
import de.scs.constants.InsulinLevel;
import de.scs.constants.Level;
import de.scs.constants.Status;
import de.scs.ui.BloodSugarStatisticsGraph;
import de.scs.ui.EventGenerator;
import de.scs.ui.FrameTemplate;
import de.scs.ui.InsulinPumpMonitor;
import de.scs.ui.Utils;

public class HealthStatusController
{

  private static InsulinPump            insulinPump;
  private static EventGenerator         eventGenerator;
  private static InsulinPumpMonitor     insulinPumpMonitor;
  private static int                    currentBloodSugarValue;
  private static int                    computedDose;
  private static int                    deliverableDose;
  protected static int                  totalInjectedDoses;
  private static int                    countDays;
  private static int                    remainingInsulin      = InsulinDoseConstants.INSULIN_CAPACITY;
  private static volatile Status        currentStatus         = Status.NORMAL;
  protected static BatteryLevel         currentBatteryLevel   = BatteryLevel.FULL;
  protected static InsulinLevel         currentInsulinLevel   = InsulinLevel.FULL;
  private static Map<String, Component> monitorComponents;
  private static Properties             props                 = new Properties();
  private static Thread                 timer;
  private static de.scs.core.Timer      pumpTimer;
  private static Map<Level, Icon>       iconMap               = new HashMap<Level, Icon>();
  private static List<String>           hardwareErrorMessages = new ArrayList<String>();
  private static int                    failureMessageNumber;
  private static JLabel                 batteryStatusLabel;
  private static JLabel                 insulinStatusLabel;
  private static JLabel                 severityLabel;
  private static JLabel                 statusField;
  private static JTextField             statusColor;
  private static JLabel                 hardwarefailureLabel;
  private static JLabel                 bloodSugarStatusLabel;
  private static JLabel                 injectedDoseLabel;
  private static JLabel                 totalDosesLabel;
  private static JLabel                 timerLabel;

  public static synchronized void changeState(int state)
  {
    currentBloodSugarValue = insulinPump.getBloodSugarValue(2);
    setBloodSugarMonitorComponents();
    switch (state)
    {
      case 1: // start timer and generate progress
        statusField.setText("No Activities");
        statusColor.setBackground(Color.GRAY);
        log.info("current blood sugar value - " + currentBloodSugarValue);
        new DataProgress(insulinPumpMonitor).generateProgress();
        if (timer == null)
        {
          new AlarmThread().start();
          pumpTimer = new de.scs.core.Timer(insulinPump.getMeasurementPeriod(), insulinPump.getHardwareCheckPeriod());
          timer = new Thread(pumpTimer);
          timer.start();
        }
        changeState(3);
        break;
      case 2: // check blood sugar level and compute dose
        int lastBloodSugarValue = insulinPump.getBloodSugarValue(1);
        int beforeLastBloodSugarValue = insulinPump.getBloodSugarValue(0);

        int days = countDays;
        countDays = (int) Math.floor(totalInjectedDoses / 25);
        if (countDays > days)
        {
          pumpTimer.setTimerSeconds(countDays > 0 && countDays % 2 == 0 ? 0 : 12 * 60 * 60);
          log.info(countDays + " day(s) passed.timer adjusted.");
          if (countDays == 4) countDays = 0;
        }

        if (currentStatus != Status.ERROR && remainingInsulin >= InsulinDoseConstants.MAX_DOSE_AT_ONCE
          && totalInjectedDoses % 25 < InsulinDoseConstants.MAX_DOSE_PER_DAY)
        {
          graph.setBloodSugarValueInGraph(currentBloodSugarValue);
          if (currentBloodSugarValue < BloodSugarLevelConstants.MINIMUM_SAFE_LEVEL)
          {
            computedDose = 0;
            currentStatus = Status.WARNING;
            log.warn("blood sugar level lower than minimum (" + currentStatus + ")");
          }
          else if (currentBloodSugarValue >= BloodSugarLevelConstants.MINIMUM_SAFE_LEVEL
            && currentBloodSugarValue <= BloodSugarLevelConstants.MAXIMUM_SAFE_LEVEL)
          {
            if (currentBloodSugarValue <= lastBloodSugarValue)
              computedDose = 0;
            else
            {
              if ((currentBloodSugarValue - lastBloodSugarValue) < (lastBloodSugarValue - beforeLastBloodSugarValue))
                computedDose = 0;
              else if ((currentBloodSugarValue - lastBloodSugarValue) >= (lastBloodSugarValue - beforeLastBloodSugarValue))
              {
                computedDose = getInsulinDoseOfRate(lastBloodSugarValue);
              }
            }
          }
          else if (currentBloodSugarValue > BloodSugarLevelConstants.MAXIMUM_SAFE_LEVEL)
          {
            if (currentBloodSugarValue > lastBloodSugarValue)
            {
              computedDose = getInsulinDoseOfRate(lastBloodSugarValue);
            }
            else if (currentBloodSugarValue == lastBloodSugarValue)
              computedDose = InsulinDoseConstants.MIN_DOSE;
            else if (currentBloodSugarValue < lastBloodSugarValue)
            {
              if ((currentBloodSugarValue - lastBloodSugarValue) <= (lastBloodSugarValue - beforeLastBloodSugarValue))
                computedDose = 0;
              else
                computedDose = InsulinDoseConstants.MIN_DOSE;
            }
          }

          if (computedDose == 0)
          {
            deliverableDose = 0;
            statusField.setText("No insulin needed.( " + timerLabel.getText() + " )");
            log.info(statusField.getText());
            statusColor.setBackground(Color.GREEN);
          }
          else if (currentInsulinLevel == InsulinLevel.EMPTY)
          {
            currentStatus = Status.ERROR;
            statusField.setText("Insulin could not be injected.( " + timerLabel.getText() + " )");
            log.info(statusField.getText() + " (" + currentStatus + ")");
            statusColor.setBackground(Color.RED);
            changeState(3);
          }
          else if (computedDose + (totalInjectedDoses % 25) > InsulinDoseConstants.MAX_DOSE_PER_DAY)
          {
            currentStatus = Status.WARNING;
            statusField.setText("Maximum daily dose exceeded.( " + timerLabel.getText() + " )");
            log.info(statusField.getText() + " (" + currentStatus + ")");
            statusColor.setBackground(Color.YELLOW);
            deliverableDose = InsulinDoseConstants.MAX_DOSE_PER_DAY - (totalInjectedDoses % 25);
            log.info("Deliverable dose: " + deliverableDose + ", Computed Dose: " + computedDose + ".");
          }
          else
          {
            currentStatus = Status.NORMAL;
            if (computedDose <= InsulinDoseConstants.MAX_DOSE_AT_ONCE)
              deliverableDose = computedDose;
            else
              deliverableDose = InsulinDoseConstants.MAX_DOSE_AT_ONCE;
          }
          totalInjectedDoses += deliverableDose;
          remainingInsulin -= deliverableDose;

          injectedDoseLabel.setText(InsulinPumpMonitor.INJECTED_DOSE_LABEL_PREFIX + deliverableDose
            + InsulinPumpMonitor.INJECTED_DOSE_LABEL_SUFFIX);
          totalDosesLabel.setText(InsulinPumpMonitor.TOTAL_DOSES_LABEL_PREFIX + totalInjectedDoses
            + InsulinPumpMonitor.TOTAL_DOSES_LABEL_SUFFIX);
          log.info("deliverable dose: " + deliverableDose + ", total injected dose: " + totalInjectedDoses);
          if (currentStatus == Status.NORMAL)
          {
            currentBloodSugarValue = (int) ((Math.random() * 30) + 90);
            graph.setBloodSugarValueInGraph(currentBloodSugarValue);
            insulinPump.setBloodSugarValue(currentBloodSugarValue);
            log.info("generated blood sugar value - " + currentBloodSugarValue + ", last bsv - " + lastBloodSugarValue
              + ", before last bsv - " + beforeLastBloodSugarValue);
          }
          setBloodSugarMonitorComponents();
          severityLabel.setIcon(iconMap.get(currentStatus));
        }
        if (remainingInsulin < InsulinDoseConstants.MAX_DOSE_AT_ONCE)
        {
          remainingInsulin = 0;
          changeState(3);
        }
        break;
      case 3:// check battery and insulin
        if (timer != null)
        {
          if (insulinPump.isBatteryCharged())
          {
            currentBatteryLevel = BatteryLevel.FULL;
            pumpTimer.setChargeSeconds(0);
            insulinPump.setBatteryCharged(false);
            batteryStatusLabel.setIcon(iconMap.get(currentBatteryLevel));
            statusField.setText("Battery charged. ( " + timerLabel.getText() + " )");
            log.info(statusField.getText());
            statusColor.setBackground(Color.GREEN);
          }
          if (insulinPump.isInsulinFilled())
          {
            currentInsulinLevel = InsulinLevel.FULL;
            remainingInsulin = InsulinDoseConstants.INSULIN_CAPACITY;
            insulinPump.setInsulinFilled(false);
            insulinStatusLabel.setIcon(iconMap.get(currentInsulinLevel));
            statusField.setText("Insulin filled.( " + timerLabel.getText() + " )");
            log.info(statusField.getText());
            statusColor.setBackground(Color.GREEN);
          }

          if (remainingInsulin <= 50) currentInsulinLevel = InsulinLevel.HALF;
          if (remainingInsulin == 0) currentInsulinLevel = InsulinLevel.EMPTY;

          switch (currentInsulinLevel)
          {
            case HALF:
              insulinStatusLabel.setIcon(iconMap.get(currentInsulinLevel));
              break;
            case EMPTY:
              insulinStatusLabel.setIcon(iconMap.get(currentInsulinLevel));
              currentStatus = Status.ERROR;
              hardwareErrorMessages.add("Insulin reservoir empty");
              statusField.setText("Insulin reservoir empty. ( " + timerLabel.getText() + " )");
              log.info(statusField.getText());
              statusColor.setBackground(Color.RED);
              changeState(5);
              break;
          }
          if (currentInsulinLevel != InsulinLevel.EMPTY) hardwareErrorMessages.remove("Insulin reservoir empty");
          switch (currentBatteryLevel)
          {
            case HALF:
            case LOW:
              batteryStatusLabel.setIcon(iconMap.get(currentBatteryLevel));
              break;
            case VERYLOW:
              batteryStatusLabel.setIcon(iconMap.get(currentBatteryLevel));
              currentStatus = Status.WARNING;
              statusField.setText("Battery almost empty. ( " + timerLabel.getText() + " )");
              log.info(statusField.getText());
              statusColor.setBackground(Color.YELLOW);
              break;
            case EMPTY:
              batteryStatusLabel.setIcon(iconMap.get(currentBatteryLevel));
              currentStatus = Status.ERROR;
              hardwareErrorMessages.add("Battery empty");
              statusField.setText("Battery empty. ( " + timerLabel.getText() + " )");
              log.info(statusField.getText());
              statusColor.setBackground(Color.RED);
              changeState(5);
              break;
          }
        }
        if (currentBatteryLevel != BatteryLevel.EMPTY) hardwareErrorMessages.remove("Battery empty");
        break;
      case 4: // check hardware
        if (!insulinPump.isSensorWorking()
          && !hardwareErrorMessages.contains(HardwareCheckConstants.HARDWARE_DEFECTIVE[1]))
        {
          hardwareErrorMessages.add(HardwareCheckConstants.HARDWARE_DEFECTIVE[1]);
          currentStatus = Status.ERROR;
        }
        else if (insulinPump.isSensorWorking())
        {
          hardwareErrorMessages.remove(HardwareCheckConstants.HARDWARE_DEFECTIVE[1]);
        }
        if (!insulinPump.isPumpWorking()
          && !hardwareErrorMessages.contains(HardwareCheckConstants.HARDWARE_DEFECTIVE[2]))
        {
          hardwareErrorMessages.add(HardwareCheckConstants.HARDWARE_DEFECTIVE[2]);
          currentStatus = Status.ERROR;
        }
        else if (insulinPump.isPumpWorking())
        {
          hardwareErrorMessages.remove(HardwareCheckConstants.HARDWARE_DEFECTIVE[2]);
        }
        if (!insulinPump.isNeedleWorking()
          && !hardwareErrorMessages.contains(HardwareCheckConstants.HARDWARE_DEFECTIVE[3]))
        {
          hardwareErrorMessages.add(HardwareCheckConstants.HARDWARE_DEFECTIVE[3]);
          currentStatus = Status.ERROR;
        }
        else if (insulinPump.isNeedleWorking())
        {
          hardwareErrorMessages.remove(HardwareCheckConstants.HARDWARE_DEFECTIVE[3]);
        }
        if (!insulinPump.isPowerSupplyWorking()
          && !hardwareErrorMessages.contains(HardwareCheckConstants.HARDWARE_DEFECTIVE[4]))
        {
          hardwareErrorMessages.add(HardwareCheckConstants.HARDWARE_DEFECTIVE[4]);
          currentStatus = Status.ERROR;
          pumpTimer.setPause(true);
        }
        else if (insulinPump.isPowerSupplyWorking())
        {
          hardwareErrorMessages.remove(HardwareCheckConstants.HARDWARE_DEFECTIVE[4]);
          pumpTimer.setPause(false);
        }

        if (hardwareErrorMessages.isEmpty())
        {
          hardwarefailureLabel.setText("No failure detected");
          hardwarefailureLabel.setForeground(Utils.setHSBColor(120, 61, 77));
        }
        if (hardwareErrorMessages.isEmpty() && currentStatus != Status.WARNING)
          currentStatus = Status.NORMAL;
        else if (currentStatus == Status.ERROR)
        {
          hardwarefailureLabel.setForeground(Utils.setHSBColor(0, 80, 72));
        }
        severityLabel.setIcon(iconMap.get(currentStatus));

      case 5:// display hardware failures
        int messagesCount = hardwareErrorMessages.size();
        String message = "";
        if (messagesCount > 0)
        {
          if (failureMessageNumber < messagesCount)
          {
            message = hardwareErrorMessages.get(failureMessageNumber);
            hardwarefailureLabel.setText(message);
            hardwarefailureLabel.setForeground(Utils.setHSBColor(0, 80, 72));
            severityLabel.setIcon(iconMap.get(currentStatus));
            log.error(hardwarefailureLabel.getText() + " (" + currentStatus + ")");
            failureMessageNumber++;
          }
          else
            failureMessageNumber = 0;
        }
        break;
    }
  }

  private static void setBloodSugarMonitorComponents()
  {
    if (currentStatus != Status.ERROR)
    {
      if (currentBloodSugarValue < BloodSugarLevelConstants.MINIMUM_SAFE_LEVEL)
      {
        currentStatus = Status.WARNING;
        bloodSugarStatusLabel.setText("Too Low");
        bloodSugarStatusLabel.setForeground(Utils.setHSBColor(60, 94, 91));
      }
      else if (currentBloodSugarValue > BloodSugarLevelConstants.MAXIMUM_SAFE_LEVEL)
      {
        currentStatus = Status.WARNING;
        bloodSugarStatusLabel.setText("Too High");
        bloodSugarStatusLabel.setForeground(Utils.setHSBColor(0, 80, 72));
      }
      else
      {
        currentStatus = Status.NORMAL;
        bloodSugarStatusLabel.setText("Normal");
        bloodSugarStatusLabel.setForeground(Utils.setHSBColor(120, 61, 77));
      }
      severityLabel.setIcon(iconMap.get(currentStatus));
    }
  }

  private static int getInsulinDoseOfRate(int lastBloodSugarValue)
  {
    int dose = (int) Math.ceil(((currentBloodSugarValue - lastBloodSugarValue)) / 40);
    if (dose == 0)
      return InsulinDoseConstants.MIN_DOSE;
    else
      return dose;
  }

  static class AlarmThread extends Thread
  {
    public void run()
    {
      try
      {
        while (true)
        {
          while (currentStatus == Status.ERROR)
          {
            URL audioFile = ClassLoader.getSystemResource(props.getProperty("ALARM_TONE"));
            Clip clip = AudioSystem.getClip();
            AudioInputStream ais = AudioSystem.getAudioInputStream(audioFile);
            clip.open(ais);
            clip.start();
            Thread.sleep(6000);
            clip.stop();
            clip.close();
            ais = AudioSystem.getAudioInputStream(audioFile);
            Thread.sleep(2500);
          }
          Thread.sleep(200);
        }
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }
  }

  public static class BSLTask extends TimerTask
  {

    private List<Integer>                bsvList;
    private boolean                      stopped;
    private InsulinPump                  insulinPump;
    private java.util.Timer              timer;
    private static int                   bsv         = 0;
    private static PropertyChangeSupport timerStatus = new PropertyChangeSupport(BSLTask.class);

    public BSLTask(List<Integer> bsvList, InsulinPump insulinPump, java.util.Timer timer)
    {
      this.bsvList = bsvList;
      this.insulinPump = insulinPump;
      this.timer = timer;
    }

    @Override
    public void run()
    {
      if (stopped)
      {
        timer.cancel();
        log.info("Reading from file finished.Timer stopped!");
      }

      if (currentStatus != Status.ERROR)
      {
        int currentValue = bsvList.get(bsv);
        insulinPump.setBloodSugarValue(currentValue);
        log.info("current blood sugar value - " + currentValue);
        bsv++;
        if (bsv == bsvList.size())
        {
          boolean lastStatus = stopped;
          stopped = true;
          timerStatus.firePropertyChange("stopped", lastStatus, stopped);
          bsv = 0;
        }
      }
    }

    public boolean isStopped()
    {
      return stopped;
    }

    public void setStopped(boolean stopped)
    {
      this.stopped = stopped;
    }

    public static void addPropertyChangeListener(PropertyChangeListener l)
    {
      timerStatus.addPropertyChangeListener(l);
    }

    public static void removePropertyChangeListener(PropertyChangeListener l)
    {
      timerStatus.removePropertyChangeListener(l);
    }
  }

  private static BloodSugarStatisticsGraph graph;

  public HealthStatusController()
  {
    log.info("Application started ( " + new Date() + " )");
    try
    {
      props.load(getClass().getResourceAsStream("/resources/constants/constants.properties"));
    }
    catch (IOException e)
    {
      System.err.println("couldn't load properties file.");
    }
    fillMap();
    insulinPump = new InsulinPump();
    eventGenerator = new EventGenerator(insulinPump);
    insulinPumpMonitor = new InsulinPumpMonitor();
    monitorComponents = InsulinPumpMonitor.getMonitorComponents();
    bloodSugarStatusLabel = (JLabel) monitorComponents.get("bloodsugarstatus");
    batteryStatusLabel = (JLabel) monitorComponents.get("batterystatus");
    insulinStatusLabel = (JLabel) monitorComponents.get("insulinstatus");
    severityLabel = (JLabel) monitorComponents.get("severity");
    statusField = (JLabel) monitorComponents.get("statusfield");
    statusColor = (JTextField) monitorComponents.get("statuscolor");
    hardwarefailureLabel = (JLabel) monitorComponents.get("hardwarefailure");
    injectedDoseLabel = (JLabel) monitorComponents.get("injecteddose");
    totalDosesLabel = (JLabel) monitorComponents.get("totaldoses");
    timerLabel = (JLabel) monitorComponents.get("timer");
    FrameTemplate ipmTemplate = new FrameTemplate(insulinPumpMonitor, "Insulin Pump Monitor",
      props.getProperty("MONITOR_ICON"), true, 0, 0).setLocation(true, null, 0, 0, false);
    execute(new IdtExecutor(ipmTemplate));
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    FrameTemplate egTemplate = new FrameTemplate(eventGenerator, "Event Generator", null, false, (int) eventGenerator
      .getPreferredSize().getWidth() + 200, ipmTemplate.getHeight());
    egTemplate.setLocation(false, null, (int) screenSize.getWidth() - ipmTemplate.getWidth() - ipmTemplate.getX()
      - egTemplate.getWidth(), ipmTemplate.getY(), false);
    execute(new IdtExecutor(egTemplate));
    graph = InsulinPumpMonitor.getGraph();
  }

  private static void execute(Runnable exe)
  {
    SwingUtilities.invokeLater(exe);
  }

  private class IdtExecutor implements Runnable
  {

    private JFrame ui;

    public IdtExecutor(JFrame ui)
    {
      this.ui = ui;
    }

    @Override
    public void run()
    {
      if (ui instanceof FrameTemplate) ((FrameTemplate) ui).showFrame(true);
    }
  }

  static
  {
    try
    {
      UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  private static Logger log = Logger.getLogger(HealthStatusController.class);

  private static void fillMap()
  {
    iconMap.put(BatteryLevel.FULL, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("FULL_BATTERY"))), 30, 30));
    iconMap.put(BatteryLevel.HALF, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("HALF_BATTERY"))), 30, 30));
    iconMap.put(BatteryLevel.LOW, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("LOW_BATTERY"))), 30, 30));
    iconMap.put(BatteryLevel.VERYLOW, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("VERY_LOW_BATTERY"))), 30, 30));
    iconMap.put(BatteryLevel.EMPTY, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("EMPTY_BATTERY"))), 30, 30));
    iconMap.put(InsulinLevel.FULL, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("FULL_INSULIN"))), 30, 30));
    iconMap.put(InsulinLevel.HALF, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("HALF_INSULIN"))), 30, 30));
    iconMap.put(InsulinLevel.EMPTY, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("EMPTY_INSULIN"))), 30, 30));
    iconMap.put(Status.NORMAL, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("NORMAL_SEVERITY"))), 120, 60));
    iconMap.put(Status.WARNING, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("WARNING_SEVERITY"))), 120, 60));
    iconMap.put(Status.ERROR, Utils.scaleImage(
      new ImageIcon(HealthStatusController.class.getResource(props.getProperty("ALARM_SEVERITY"))), 120, 60));
  }
}
